import torch
import torch.nn as nn

class Bottleneck(nn.Module):
    extension = 4

    def __init__(self, inchannel, outchannel, stride, downsample=None):
        super(Bottleneck, self).__init__()
        self.conv1 = nn.Conv2d(inchannel, outchannel, kernel_size=1, stride=stride, bias=False)
        self.bn1 = nn.BatchNorm2d(outchannel)
        self.conv2 = nn.Conv2d(outchannel, outchannel, kernel_size=3, stride=1, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(outchannel)
        self.conv3 = nn.Conv2d(outchannel, outchannel * self.extension, kernel_size=1, stride=1, bias=False)
        self.bn3 = nn.BatchNorm2d(outchannel * self.extension)
        self.relu = nn.ReLU(inplace=True)

        self.downsample = downsample
        self.stride = stride

    def forward(self, x):
        residual = x
        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)

        out = self.conv3(out)
        out = self.bn3(out)
        out = self.relu(out)

        if self.downsample is not None:
            residual = self.downsample(x)

        out = out + residual
        out = self.relu(out)
        return out


class ResNet(nn.Module):
    def __init__(self, block, layers, num_class=100):
        self.inchannel = 256
        super(ResNet, self).__init__()
        self.block = block
        self.layers = layers
        self.conv1 = nn.Conv2d(100, self.inchannel, kernel_size=3, stride=1, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(self.inchannel)
        self.relu = nn.ReLU()

        self.stage1 = self.make_layer(self.block, 256, layers[0], stride=1)
        self.stage2 = self.make_layer(self.block, 512, layers[1], stride=2)
        self.stage3 = self.make_layer(self.block, 1024, layers[2], stride=2)

        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.fc = nn.Linear(1024 * block.extension, num_class)

    def make_layer(self, block, outchannel, block_num, stride=1):
        block_list = []
        downsample = None
        if (stride != 1 or self.inchannel != outchannel * block.extension):
            downsample = nn.Sequential(
                nn.Conv2d(self.inchannel, outchannel * block.extension, stride=stride, kernel_size=1, bias=False),
                nn.BatchNorm2d(outchannel * block.extension)
            )

        conv_block = block(self.inchannel, outchannel, stride=stride, downsample=downsample)
        block_list.append(conv_block)
        self.inchannel = outchannel * block.extension

        for i in range(1, block_num):
            block_list.append(block(self.inchannel, outchannel, stride=1))
        return nn.Sequential(*block_list)

    def forward(self, x):
        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.stage1(out)
        out = self.stage2(out)
        out = self.stage3(out)

        out = self.avgpool(out)
        out = torch.flatten(out, 1)
        out = self.fc(out)
        return out


def resnet():
    return ResNet(Bottleneck, [3,3,3])